package com.zt.teacher.netty;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.zt.common.pojo.SignIn;
import com.zt.common.pojo.SignInItem;
import com.zt.common.pojo.StudentVx;
import com.zt.common.pojo.Teachers;
import com.zt.common.pojo.netty.Message;
import com.zt.common.pojo.exam.ExamAnswer;
import com.zt.common.pojo.exam.ExamQuestionBank;
import com.zt.common.pojo.netty.Sender;
import com.zt.common.utils.JwtUtil;
import com.zt.common.utils.RedisUtil;
import com.zt.teacher.service.*;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * description:
 * author:zt
 * date:2021-09-24
 */

//teacher:{"send":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjdXJyZW50IjoiMTY0Mjg2NjcxODgwOSIsImlkIjoiMjAxMDEwMDMxMjYiLCJleHAiOjE2NDI4Njg1MTh9.saMglHYyHwjEm3_FYkTh5PeZKL2GbKGTx7YU_U4Bi-8","receive":1,"info":"haha","type":1}
//student:{"send":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjdXJyZW50IjoiMTY0Mjg2NjcxODk2MSIsImlkIjoiMjAxODEwMDMxMjYiLCJleHAiOjE2NDI4Njg1MTh9.xqsZElrr158iP3n-3PoF1r8LYLpl2jrEAXT5i_JGAKI","receive":1,"info":"haha","type":1}

@Component
@ChannelHandler.Sharable //Sharable 所有通道都能使用的handler
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private TeacherService teacherService;
    @Autowired
    private StudentVxService studentVxService;
    @Autowired
    private OriginStudentService originStudentService;
    @Autowired
    private ExamQuestionService examQuestionService;
    @Autowired
    private ExamQuestionBankService examQuestionBankService;
    @Autowired
    private ExamAnswerService examAnswerService;
    @Autowired
    private SignInService signInService;
    @Autowired
    private SignInItemService signInItemService;

    private static final Log log = LogFactory.get();

    //班级id 对应channelGroup
    private static final Map<Integer,ChannelGroup> CHANNEL_GROUP_MAP = new ConcurrentHashMap<>();
    //CLASS_MAP 与 USER_MAP 为了用户离线能够通知到group
    //channelId对应班级id
    private static final Map<String,Integer> CLASS_MAP = new ConcurrentHashMap<>();
    //channelId对应用户id（send字段）
    private static final Map<String,String> USER_MAP = new ConcurrentHashMap<>();
    //TEACH_MAP 为了能够通知到老师
    //班级id 对应老师channelId
    private static final Map<Integer,Channel> TEACH_MAP = new ConcurrentHashMap<>();


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("异常发生：" + cause.getMessage());
        ctx.close();
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("handlerAdded被调用" + ctx.channel().id().asLongText());
        CHANNEL_GROUP_MAP.keySet().forEach(key -> log.info("当前正在上课班级id：" + key));
        TEACH_MAP.forEach((key, value) -> log.info("当前正在上课班级：" + key +"对应的老师：" + USER_MAP.get(value.id().asLongText())));
        log.info("当前总共在线人数：" + USER_MAP.size());
    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        String channelId = channel.id().asLongText();

        log.info("handlerRemoved被调用" + channelId);

        Integer classId = CLASS_MAP.get(channelId);
        //证明不是正常通道 直接返回
        if (classId == null) return;
        //未下课时用户离线 通知教师
        if (CHANNEL_GROUP_MAP.containsKey(classId)){
            CHANNEL_GROUP_MAP.get(classId).remove(channel);
            TEACH_MAP.get(classId).writeAndFlush(new TextWebSocketFrame(
                    JSON.toJSONString(new Message().setSend(USER_MAP.get(channelId)).setType(-2).setOperation(1))));
        }
        //todo 老师离线

        //移除channel所关联的用户和班级信息
        USER_MAP.remove(channelId);
        CLASS_MAP.remove(channelId);


        CHANNEL_GROUP_MAP.keySet().forEach(key -> log.info("当前正在上课班级id：" + key));
        TEACH_MAP.forEach((key, value) -> log.info("当前正在上课班级：" + key +"对应的老师：" + USER_MAP.get(value.id().asLongText())));
        log.info("当前总共在线人数：" + USER_MAP.size());
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        Channel channel = ctx.channel();
        //收到的消息
        Message message = JSON.parseObject(msg.text(), Message.class);
        //返回消息
        //type -1错误信息 -2学生离线 -3已开启禁言 -4签到过期
        Message reMes = new Message().setOperation(2);


        /*处理发送消息前的检查*/
        if (message.getOperation() != 1) {
            reMes.setType(-1).setInfo("消息操作有误");
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
            return;
        }
        //校验班级是否正在上课
        Integer classId = message.getReceive();
        if (classId==null) {
            reMes.setInfo("未指定接收方，请重试").setType(-1);
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
            return;
        }
        //获取接收方班级channelGroup
        ChannelGroup channelGroup = CHANNEL_GROUP_MAP.get(classId);
        if (channelGroup == null) {
            reMes.setInfo("班级" + classId + "未在上课中").setType(-1);
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
            return;
        }
        //处理发送方字段
        boolean isTeacher = isTeacher(message.getSend());
        if (USER_MAP.containsKey(channel.id().asLongText())){
            message.setSend(USER_MAP.get(channel.id().asLongText()));
        }
        else {
            try {
                Sender sender = new Sender();
                if (!isTeacher){
                    //将发送方字段修改为学生微信头像与学号
                    StudentVx student = studentVxService.getStudentByStuId(JwtUtil.decodedGetID(message.getSend()));
                    sender.setId(student.getStudentId()).setAvatar(student.getAvatar())
                            .setName(originStudentService.getOriginStuById(student.getStudentId()).getStudentName());
                }else {
                    //老师修改为老师工号
                    Teachers teacher = teacherService.findTeacherById(JwtUtil.decodedGetID(message.getSend()));
                    sender.setId(teacher.getTeacherId()).setName(teacher.getName());
                }
                message.setSend(JSON.toJSONString(sender));
            }catch (Exception e){
                reMes.setInfo("token有误").setType(-1);
                channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
                return;
            }
        }

        log.info("客户端发送：" + message);

        /*处理发送消息*/
        switch (message.getType()){
            //聊天 text为聊天内容
            case 1:{
                if (!check(channelGroup,channel)) return;

                //老师可以在禁言状态下发言
                if (channel != TEACH_MAP.get(classId)){
                    if (redisUtil.hasKey(classId + "-mute")){
                        reMes.setType(-3);
                        channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
                        return;
                    }
                }

                channelGroup.forEach(ch -> {
                    if (channel != ch){  //不是当前发送消息的channel，那么转发
                        ch.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                    }
                });
                break;
            }
            //绑定班级 无text
            case 2:{
                if (isTeacher){
                    TEACH_MAP.put(classId,channel);

                    reMes.setInfo(message.getInfo()).setType(message.getType());
                    channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));

                    //判断是否是特殊情况即老师是重新加入的
                    HashMap<String, Object> map = new HashMap<>();
                    reMes.setType(0).setInfo(map);

                    if (CHANNEL_GROUP_MAP.get(classId).size() != 0){
                        //获取在线成员信息
                        ArrayList<Sender> online = new ArrayList<>();
                        CHANNEL_GROUP_MAP.get(classId).forEach(ch -> {
                            Sender sender = JSON.parseObject(USER_MAP.get(ch.id().asLongText()), Sender.class);
                            online.add(sender);
                        });
                        map.put("online",online);

                        //判断是否有签到
                        if (!redisUtil.hasKey(classId + "-signIn")){
                            //未开始签到
                            map.put("signIn",null);
                        }else {
                            //获取已签到学生的学号
                            ArrayList<String> signedInMember = new ArrayList<>();
                            int signInId = (int) redisUtil.get(classId + "-signIn");
                            List<SignInItem> signInItems = signInItemService.getBySignInId(signInId);
                            signInItems.forEach(signInItem -> signedInMember.add(signInItem.getStudentId()));
                            map.put("signInMember",signedInMember);

                            if (redisUtil.hasKey(classId + "-signIn-expire")){
                                //正在签到
                                map.put("signIn",redisUtil.getExpire(classId + "-signIn-expire"));
                            }else {
                                //签到已结束
                                map.put("signIn",null);
                            }
                        }

                        if (!redisUtil.hasKey(classId + "-exam")){
                            //未开始考试
                            map.put("examQuestion",null);
                        }else {
                            Integer examId = (Integer) redisUtil.get(classId + "-exam");
                            //获取当前考试题目
                            List<Integer> questionIds = examQuestionService.getQuestionIdsByExamId(examId);
                            List<ExamQuestionBank> questions = examQuestionBankService.getQuestionByIds(questionIds);
                            map.put("examQuestion",questions);

                            List<ExamAnswer> answers = examAnswerService.getAnswerByQuestionIds(questionIds,examId);
                            //每道题目的选择情况 choice：key=题目id；value={key=选项；value=选择人数}
                            HashMap<String, HashMap<String,Integer>> choice = new HashMap<>();
                            for (ExamAnswer answer : answers) {
                                HashMap<String, Integer> qu = choice.getOrDefault(String.valueOf(answer.getQuestionId()), new HashMap<>());
                                qu.put(answer.getAnswer(),qu.getOrDefault(answer.getAnswer(),0) + 1);
                                choice.put(String.valueOf(answer.getQuestionId()),qu);
                            }
                            map.put("examAnswer",choice);

                            if (redisUtil.hasKey(classId + "-exam-currentId")){
                                //正在考试 获取当前老师发布题目id
                                map.put("examCurrentId",redisUtil.get(classId + "-exam-currentId"));
                            }else {
                                map.put("examCurrentId",null);
                            }
                        }

                        if (redisUtil.hasKey(classId + "-mute")){
                            map.put("mute",true);
                        }else {
                            map.put("mute",false);
                        }
                        channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
                    }
                }
                else {
                    //如果是学生 则提示老师
                    TEACH_MAP.get(classId).writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                    //返回发送者 发送成功信息
                    reMes.setInfo(message.getInfo()).setType(message.getType());
                    channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));

                    //判断是否有考试或者签到
                    HashMap<String, Object> map = new HashMap<>();
                    reMes.setType(0).setInfo(map);

                    if (!redisUtil.hasKey(classId + "-signIn")){
                        //未开始签到
                        map.put("signIn",null);
                    }else {
                        Sender sender = JSON.parseObject(message.getSend(), Sender.class);
                        SignInItem signInItem = signInItemService.getBySignInIdAndStuId((Integer) redisUtil.get(classId + "-signIn"), sender.getId());

                        if (redisUtil.hasKey(classId + "-signIn-expire")){
                            if (signInItem == null) {
                                //正在签到 且未签到
                                map.put("signIn",redisUtil.getExpire(classId + "-signIn-expire"));
                            }else {
                                map.put("signIn",true);
                            }
                        }else {
                            //签到已结束
                            map.put("signIn",true);
                        }
                    }

                    if (!redisUtil.hasKey(classId + "-exam")){
                        //未开始考试
                        map.put("examQuestion",null);
                    }else {
                        Integer examId = (Integer) redisUtil.get(classId + "-exam");
                        //获取当前考试题目
                        List<Integer> questionIds = examQuestionService.getQuestionIdsByExamId(examId);
                        List<ExamQuestionBank> questions = examQuestionBankService.getQuestionByIds(questionIds);

                        map.put("examQuestion",questions);
                        //获取回答情况
                        Sender sender = JSON.parseObject(message.getSend(), Sender.class);
                        List<ExamAnswer> answers = examAnswerService.getAnswerByQuestionIdsAndStuId(sender.getId(), questionIds,examId);
                        map.put("examAnswer",answers);

                        if (redisUtil.hasKey(classId + "-exam-currentId")){
                            //正在考试 获取当前老师发布题目id
                            map.put("examCurrentId",redisUtil.get(classId + "-exam-currentId"));
                        }else {
                            map.put("examCurrentId",null);
                        }
                    }

                    if (redisUtil.hasKey(classId + "-mute")){
                        map.put("mute",true);
                    }else {
                        map.put("mute",false);
                    }
                    channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
                }

                channelGroup.add(channel);
                CHANNEL_GROUP_MAP.put(classId,channelGroup);
                USER_MAP.put(channel.id().asLongText(),message.getSend());
                CLASS_MAP.put(channel.id().asLongText(),classId);
                return;
            }
            //发起签到 text为签到时间
            case 3:{
                if (!check(channelGroup,channel,isTeacher)) return;

                SignIn signIn = new SignIn();
                signIn.setActiveClassId(classId).setCreateTime(new Date());
                signInService.insert(signIn);

                redisUtil.set(classId + "-signIn-expire",1,Long.parseLong(String.valueOf(message.getInfo())));
                redisUtil.set(classId + "-signIn",signIn.getId());

                //老师发起签到 向班级学生发送签到信息
                channelGroup.forEach(ch -> {
                    if (ch != channel){
                        ch.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                    }
                });
                break;
            }
            //签到 无text
            case 4:{
                if (!check(channelGroup,channel)) return;

                if (redisUtil.hasKey(classId + "-signIn-expire")){
                    Sender sender = JSON.parseObject(message.getSend(), Sender.class);
                    signInItemService.insert(new SignInItem((Integer) redisUtil.get(classId + "-signIn"), sender.getId()));

                    TEACH_MAP.get(classId).writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                }else {
                    reMes.setInfo("签到已过期").setType(-4);
                    channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
                    return;
                }
                break;
            }
            //老师选题完成，发布考试 text为考试题目
            case 5:{
                if (!check(channelGroup,channel,isTeacher)) return;

                //关联老师选择出来的试题与考试
                ArrayList<Integer> questionIds = JSON.parseObject(JSONArray.toJSONString(message.getInfo()),ArrayList.class);
                int examId = (int) redisUtil.get(classId + "-exam");
                examQuestionService.insert(examId,questionIds);
                //获取试题内容
                List<ExamQuestionBank> questions = examQuestionBankService.getQuestionByIds(questionIds);
                message.setInfo(questions);

                //当前发布题目存为考试题目的第一题
                redisUtil.set(classId + "-exam-currentId",questionIds.get(0));

                //向学生转发
                channelGroup.forEach(ch -> {
                    if (ch != channel){
                        ch.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                    }
                });
                break;
            }
            //考试切换题目
            case 6:{
                if (!check(channelGroup,channel,isTeacher)) return;
                //更新当前发布题目
                redisUtil.set(classId + "-exam-currentId",message.getInfo());
                //向学生转发
                channelGroup.forEach(ch -> {
                    if (ch != channel){
                        ch.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                    }
                });
                break;
            }
            //老师结束考试
            case 7:{
                if (!check(channelGroup,channel,isTeacher)) return;

                if (!redisUtil.hasKey(classId + "-exam-currentId")){
                    reMes.setInfo("考试未开启").setType(-1);
                    channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
                    return;
                }

                redisUtil.del(classId + "-exam-currentId");
                //向学生转发
                channelGroup.forEach(ch -> {
                    if (ch != channel){
                        ch.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                    }
                });
                break;
            }
            //学生考试作答
            case 8:{
                if (!check(channelGroup,channel)) return;
                //插入一条数据库记录
                ExamAnswer answer = JSONObject.parseObject(JSON.toJSONString(message.getInfo()), ExamAnswer.class);
                //将发送方字段的学生id设置是为 作答者id
                Sender sender = JSON.parseObject(message.getSend(), Sender.class);
                answer.setStudentId(sender.getId()).setExamId((int) redisUtil.get(classId + "-exam"));
                examAnswerService.insert(answer);

                TEACH_MAP.get(classId).writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                break;
            }
            //老师设置禁言
            case 9:{
                if (!check(channelGroup,channel,isTeacher)) return;

                redisUtil.set(classId + "-mute",1);
                //向学生转发
                channelGroup.forEach(ch -> {
                    if (ch != channel){
                        ch.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                    }
                });
                break;
            }
            //老师解除禁言
            case 10:{
                if (!check(channelGroup,channel,isTeacher)) return;

                if (!redisUtil.hasKey(classId + "-mute")){
                    reMes.setInfo("未设置禁言").setType(-1);
                    channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
                    return;
                }
                redisUtil.del(classId + "-mute");
                //向学生转发
                channelGroup.forEach(ch -> {
                    if (ch != channel){
                        ch.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                    }
                });
                break;
            }
            default:break;
        }
        //定义返回发送者消息
        reMes.setInfo(message.getInfo()).setType(message.getType());
        channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(reMes)));
    }

    public void attendClass(int classId){
        CHANNEL_GROUP_MAP.put(classId,new DefaultChannelGroup(GlobalEventExecutor.INSTANCE));
        log.info("班级：" + classId + "开启上课");
    }

    //班级下课 清除该班级所有信息
    public void removeClass(int classId){
        ChannelGroup channelGroup = CHANNEL_GROUP_MAP.get(classId);

        Channel teacherChannel = TEACH_MAP.get(classId);
        //type 12 老师下课
        if (teacherChannel != null) {
            Message message = new Message().setType(12).setSend(USER_MAP.get(teacherChannel.id().asLongText()));
            channelGroup.forEach(ch -> {
                if (ch != teacherChannel) ch.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
            });
        }

        CHANNEL_GROUP_MAP.remove(classId);
        TEACH_MAP.remove(classId);

        if (redisUtil.hasKey(classId + "正在上课"))
            redisUtil.del(classId + "正在上课");
        if (redisUtil.hasKey(classId + "-signIn"))
            redisUtil.del(classId + "-signIn");
        if (redisUtil.hasKey(classId + "-exam"))
            redisUtil.del(classId + "-exam");
        if (redisUtil.hasKey(classId + "-mute"))
            redisUtil.del(classId + "-mute");


        log.info("班级：" + classId + "结束上课");
    }

    //判断该课堂是否正在上课
    public boolean isHavingClass(int classId){
        return CHANNEL_GROUP_MAP.containsKey(classId) || redisUtil.hasKey(classId + "正在上课");
    }

    public boolean isTeacher(String token){
        String id = JwtUtil.decodedGetID(token);
        return teacherService.findTeacherById(id)!=null;
    }

    //当老师发布资源时提醒学生
    public void remindStudents(int classId){
        Channel teacherChannel = TEACH_MAP.get(classId);

        CHANNEL_GROUP_MAP.get(classId).forEach(ch ->{
            //只发送给学生
            if (ch != teacherChannel) {
                //type 11 老师发布资源
                Message message = new Message().setType(11).setSend(USER_MAP.get(teacherChannel.id().asLongText()));
                ch.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
            }
        });
    }

    private boolean check(ChannelGroup channelGroup,Channel channel,boolean isTeacher){
        Message mes = new Message();
        if (!channelGroup.contains(channel)) {
            mes.setType(-1).setInfo("您不属于您发送的消息的接收班级，请核验后重试");
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(mes)));
            return false;
        }
        if (!isTeacher){
            mes.setType(-1).setInfo("没有教师权限");
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(mes)));
            return false;
        }
        return true;
    }
    private boolean check(ChannelGroup channelGroup,Channel channel){
        Message mes = new Message();
        if (!channelGroup.contains(channel)) {
            mes.setType(-1).setInfo("您不属于您发送的消息的接收班级，请核验后重试");
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(mes)));
            return false;
        }
        return true;
    }

}
